module shader.litshadersphharm;

import render.render;
import scene.scene;
import scene.ray;
import scene.intersection;
import shader.environment;
import shader.abstractshader;
import std.math;
import utils.color;
import utils.vector2;
import utils.vector3;
import utils.mt19937ar;

float sinc(float x) {               /* Supporting sinc function */
  if (fabs(x) < 1.0e-4) return 1.0 ;
  else return(sin(x)/x) ;
}

class LitShaderSphHarmonics: AbstractShader {
    const float SHADE_EPSILON = 0.01f;

    private Environment mEnvironment;
    private RGBColor[9] coeffs;

    private void updatecoeffs(RGBColor hdr, float domega, float x, float y, float z) {

      /******************************************************************
       Update the coefficients (i.e. compute the next term in the
       integral) based on the lighting value hdr[3], the differential
       solid angle domega and cartesian components of surface normal x,y,z

       Inputs:  hdr = L(x,y,z) [note that x^2+y^2+z^2 = 1]
                i.e. the illumination at position (x,y,z)

                domega = The solid angle at the pixel corresponding to
            (x,y,z).  For these light probes, this is given by

            x,y,z  = Cartesian components of surface normal

       Notes:   Of course, there are better numerical methods to do
                integration, but this naive approach is sufficient for our
            purpose.

      *********************************************************************/

        float c ; /* A different constant for each coefficient */

        /* L_{00}.  Note that Y_{00} = 0.282095 */
        c = 0.282095 ;
        coeffs[0] += hdr*c*domega ;

        /* L_{1m}. -1 <= m <= 1.  The linear terms */
        c = 0.488603 ;
        coeffs[1] += hdr*(c*y)*domega ;   /* Y_{1-1} = 0.488603 y  */
        coeffs[2] += hdr*(c*z)*domega ;   /* Y_{10}  = 0.488603 z  */
        coeffs[3] += hdr*(c*x)*domega ;   /* Y_{11}  = 0.488603 x  */

        /* The Quadratic terms, L_{2m} -2 <= m <= 2 */

        /* First, L_{2-2}, L_{2-1}, L_{21} corresponding to xy,yz,xz */
        c = 1.092548 ;
        coeffs[4] += hdr*(c*x*y)*domega ; /* Y_{2-2} = 1.092548 xy */
        coeffs[5] += hdr*(c*y*z)*domega ; /* Y_{2-1} = 1.092548 yz */
        coeffs[7] += hdr*(c*x*z)*domega ; /* Y_{21}  = 1.092548 xz */

        /* L_{20}.  Note that Y_{20} = 0.315392 (3z^2 - 1) */
        c = 0.315392 ;
        coeffs[6] += hdr*(c*(3*z*z-1))*domega ;

        /* L_{22}.  Note that Y_{22} = 0.546274 (x^2 - y^2) */
        c = 0.546274 ;
        coeffs[8] += hdr*(c*(x*x-y*y))*domega ;
    }

    public void getColor(inout RenderContext context) {
        const float c1 = 0.429043f;
        const float c2 = 0.511664f;
        const float c3 = 0.743125f;
        const float c4 = 0.886227f;
        const float c5 = 0.247708f;
        float x = context.rinters.normal.x;
        float y = context.rinters.normal.y;
        float z = context.rinters.normal.z;

        RGBColor colE = c1*coeffs[8]*(x*x-y*y) + c3*coeffs[6]*z*z + c4*coeffs[0] - c5*coeffs[6]
            + 2.0f*c1*(coeffs[4]*x*y + coeffs[7]*x*z + coeffs[5]*y*z)
            + 2.0f*c2*(coeffs[3]*x + coeffs[1]*y + coeffs[2]*z);

        context.addDirectionalLight(colE,context.rinters.normal);
    }

    public void environment(Environment env) {
        mEnvironment = env;
    }

    //no callbacks in Environment, call this to update
    public void updateEnv() {
        for (int i = 0; i < 9; i++)
            coeffs[i] = RGBColor(0);

        assert(mEnvironment.texture.width == mEnvironment.texture.height);
        int width = mEnvironment.texture.width;

        for (int x = 0; x < width; x++) {
            for (int y = 0; y < width; y++) {
                float u = (x-width/2.0)/(width/2.0);    /* u ranges from -1 to 1 */
                float v = (width/2.0 - y)/(width/2.0);  /* v ranges from -1 to 1 */

                float r = sqrt(u*u+v*v);

                if (r <= 1.0f) {
                    float theta = PI*r;
                    float phi = atan2(v,u);

                    float s_x = sin(theta)*cos(phi);
                    float s_y = sin(theta)*sin(phi);
                    float s_z = cos(theta);

                    float domega = (2*PI/width)*(2*PI/width)*sinc(theta) ;

                    RGBColor colL = mEnvironment.texture.getPixelColor(Vector2i(x,y));
                    updatecoeffs(colL,domega,s_x,s_y,s_z);
                }
            }
        }

        for (int i = 0; i < 9; i++)
            std.stdio.writefln("%s",coeffs[i]);
    }

    public char[] toString() {
        return "Fast Irradiance (Spherical Harmonics)";
    }
}
